﻿using System;
using System.Collections.Generic;
using MLSharp.Utilities;

namespace MLSharp
{
	/// <summary>
	/// Provides a way to take random samples from a 
	/// <see cref="IDataSet"/>.
	/// </summary>
	public class Sample
	{
		#region Private Static Fields

		/// <summary>
		/// The default seed used by instances of this type.  A static seed 
		/// insures that the behavior is repeatable. 
		/// </summary>
		private const int DEFAULT_SEED = 1;

		#endregion

		#region Private Fields

		/// <summary>
		/// Stores the instances in the sample.
		/// </summary>
		private IList<Instance> mInstances;

		/// <summary>
		/// Used to generate random numbers.
		/// </summary>
		private Random mRand;

		#endregion

		#region Public Properties

		/// <summary>
		/// Gets the number of items remaining in the sample.
		/// </summary>
		public int Size
		{
			get { return mInstances == null ? 0 : mInstances.Count; }
		}

		#endregion

		#region Public Constructors

		/// <summary>
		/// Instantiates a sample to pull from the specified
		/// data set with a static seed.  
		/// </summary>
		/// <param name="instances"></param>
		public Sample(IEnumerable<Instance> instances) : this(instances, false)
		{
			
		}

		/// <summary>
		/// Instantiates a sample to pull from the specified
		/// data set.
		/// </summary>
		/// <param name="instances">The data set to use.</param>
		/// <param name="useRandomSeed">True to use a random seed, false otherwise.</param>
		public Sample(IEnumerable<Instance> instances, bool useRandomSeed)
		{
			mInstances = new List<Instance>(instances);

			if (useRandomSeed)
			{
				mRand = new Random();
			}
			else
			{
				mRand = new Random(DEFAULT_SEED);
			}
		}

		#endregion

		#region Public Static Methods

		/// <summary>
		/// Creates a subsample of the specified size from the specified data set.
		/// </summary>
		/// <param name="dataSet">The data set.</param>
		/// <param name="size">The subsample size (in the (0,1) range).</param>
		/// <returns>The new sample</returns>
		public static IDataSet CreateSubsample(IDataSet dataSet, double size)
		{
			if (size <=0 || size >= 1)
			{
				throw new ArgumentOutOfRangeException("size", size, "Must be greater than 0 and less than 1.");
			}

			if (dataSet.Instances.Count == 0)
			{
				throw new ArgumentException("Data set must contain at least one instance.", "dataSet");
			}

			int targetSize = (int)(dataSet.Instances.Count*size);

			BasicDataSet sample = new BasicDataSet();
			sample.Attributes.AddRange(dataSet.Attributes);

			Sample sampler = new Sample(dataSet.Instances);

			for (int i = 0; i < targetSize; i++)
			{
				sample.Instances.Add(sampler.SampleWithoutReplacement().Clone());
			}

			return sample;
		}

		#endregion

		#region Public Methods

		/// <summary>
		/// Pulls a random instance and returns it. The item is removed from
		/// the sample.
		/// </summary>
		/// <returns>The instance.</returns>
		/// <exception cref="InvalidOperationException">Thrown if there are no more instances in the sample.</exception>
		public Instance SampleWithoutReplacement()
		{
			if (mInstances == null || mInstances.Count == 0)
			{
				throw (new InvalidOperationException("There are no more instances in the sample."));
			}

			int index = mRand.Next(0, mInstances.Count);

			Instance instance = mInstances[index];
			mInstances.RemoveAt(index);

			return instance;
		}

		#endregion
	}
}
